Transform = {
    name = "Transform",
    props = {
        _interactable = false,
        _x = 0,
        _y = 0,
        _r = 0,
        _sx = 1,
        _sy = 1,
        _ax = 0.5,
        _ay = 0.5,
        _width = 100,
        _height = 100,
        kx = 0,
        ky = 0,
        wx = 0,
        wy = 0,
        wr = 0,
        wsx = 1,
        wsy = 1,
    },
	ctor = function(self)
	    self.children = {}
	    self.touchSwallowed = true
	    self.active = true
	end,
	gets = {
        interactable = function (self)
            return self._interactable
        end,
        width = function (self)
            return self._width
        end,
        height = function (self)
            return self._height
        end
    },
	sets = {
        interactable = function (self, k, v)
            self._interactable = v
            self.subStyle = v and "none" or "forbidden"
            self:emit(NodeEvent.InteractableChanged, v)
        end,
        width = function (self, k, v)
            self._width = v
            self:emit(NodeEvent.WidthChanged)
            self:emit(NodeEvent.WidthDirty)
        end,
        height = function (self, k, v)
            self._height = v
            self:emit(NodeEvent.HeightChanged)
            self:emit(NodeEvent.HeightDirty)
        end
    }
}
---@diagnostic disable-next-line: deprecated
local keys = {"x", "y", "r", "sx", "sy", "ax", "ay"}
for _, k in ipairs(keys) do
	Transform.gets[k] = load("return function(self, k) return self._"..k.." end")()
	Transform.sets[k] = load("return function(self, k, v) self._"..k.." = v self:setTransformDirty() end")()
end

local scissors = {}
local function pushScissor(args)
    table.insert(scissors, args)
end

local function popScissor()
    table.remove(scissors, #scissors)
end
local function scissorTest(scissor, xx, yy)
    local x, y, w, h = unpack(scissor)
    return xx >= x and yy >= y and xx <= x + w and yy <= y + h
end
function Transform:add(child)
    if child.parent == self then
        return
    end
    if child.parent then
        child.parent:remove(child)
    end
    table.insert(self.children, child)
    child:setParent(self)
    self:emit(NodeEvent.AddChild, child)
    return self
end

function Transform:setParent(parent)
    local old = self.parent
    self.parent = parent
    self:setTransformDirty()
    self:emit(NodeEvent.SetParent, parent, old)
    return self
end

function Transform:insert(child, pos)
    table.insert(self.children, pos, child)
    child.parent = self
    child:setTransformDirty()
    return self
end

function Transform:insertTo(parent, pos)
    parent:insert(self, pos)
    return self
end

function Transform:addTo(parent)
    parent:add(self)
    return self
end

function Transform:removeFromParent()
    if self.parent then
        self.parent:remove(self)
    end
end

function Transform:removeAt(i)
    local child = table.remove(self.children, i)
    self:emit(NodeEvent.RemoveChild, child)
    child:setParent(nil)
    return self
end

function Transform:remove(child)
    for i = #self.children, 1, -1 do
        if self.children[i] == child then
            table.remove(self.children, i)
            break
        end
    end
    self:emit(NodeEvent.RemoveChild, child)
    child:setParent(nil)
end

function Transform:setTransformDirty()
    self:setTransformDirtySelf()
    for _, child in ipairs(self.children) do
        child:setTransformDirty()
    end
end

function Transform:setTransformDirtySelf()
    self.transformDirty = true
    self:emit(NodeEvent.TransformDirty)
end

function Transform:getWxy()
    self:checkUpdateTransform()
    return self.wx, self.wy
end

function Transform:checkUpdateTransform()
    if self.transformDirty then
        local dirtyTransforms = {self}
        local cur = self
        while cur.parent and cur.parent.transformDirty do
            cur = cur.parent
            table.insert(dirtyTransforms, cur)
        end
        for i = #dirtyTransforms, 1, -1 do
            dirtyTransforms[i]:_updateTransform()
        end
        return true
    end
    return false
end

function Transform:_updateTransform()
    if self.parent then
        local p = self.parent
        TmpTransform:setTransformation(p.wx, p.wy, p.wr, p.wsx, p.wsy, 0, 0, p.kx, p.ky)
        self.wx, self.wy = TmpTransform:transformPoint(self.x, self.y)

        TmpTransform:setTransformation(0, 0, p.wr, p.wsx, p.wsy, 0, 0, p.kx, p.ky)
        self.wsx, self.wsy = TmpTransform:transformPoint(self.sx, self.sy)
    else
        self.wx, self.wy = self.x, self.y
        self.wsx, self.wsy = self.sx, self.sy
        self.wr = self.r
    end
    self.transformDirty = false
end

function Transform:getDrawInfo()
    self:checkUpdateTransform()
    TmpTransform:setTransformation(self.wx, self.wy, self.wr, self.wsx, self.wsy, 0, 0, self.kx, self.ky)
    local left = -self.ax * self.width
    local top = -self.ay * self.height
    local x, y = TmpTransform:transformPoint(left, top)
    return x, y, self.wr, self.wsx, self.wsy, self.kx, self.ky
end

function Transform:left(x)
    self.x = x + self.ax * self.width * self.sx
end

function Transform:right(x)
    self.x = x - (1 - self.ax) * self.width * self.sx
end

function Transform:xCenter(x)
    self.x = x + (self.ax - 0.5) * self.width * self.sx
end

function Transform:top(y)
    self.y = y + self.ay * self.height * self.sy
end

function Transform:bottom(y)
    self.y = y - (1 - self.ay) * self.height * self.sy
end

function Transform:yCenter(y)
    self.y = y + (self.ay - 0.5) * self.height * self.sy
end

function Transform:draw()
    if self.renderer then
        self.renderer:draw()
    end
end

function Transform:onPressed(fn, caller)
    self.interactable = true
    self:on(NodeEvent.Pressed, fn, caller)
end

function Transform:onReleased(fn, caller)
    self:on(NodeEvent.Released, fn, caller)
end
function Transform:hitTest(x, y)
    if #scissors > 0 then
        for i = #scissors, 1, -1 do
            local scissor = scissors[i]
            if not scissorTest(scissor, x, y) then
                return false
            end
        end
    end
    local left = self.wx - self.width * self.wsx * self.ax
    local right = left + self.width * self.wsx
    local top = self.wy - self.height * self.wsy * self.ay
    local bottom = top + self.height * self.wsy
    return left <= x and x <= right and top <= y and y <= bottom
end

function Transform:toLocal(wx, wy)
    self:checkUpdateTransform()
    local dx = wx - self.wx
    local dy = wy - self.wy
    return dx / self.wsx, dy / self.wsy
end

function Transform:toWorld(lx, ly)
    self:checkUpdateTransform()
    TmpTransform:setTransformation(self.wx, self.wy, self.wr, self.wsx, self.wsy, 0, 0, self.kx, self.ky)
    local x, y = TmpTransform:transformPoint(lx, ly)
    return x, y
end

function Transform:localLeft()
    return -self.width * self.ax
end

function Transform:localRight()
    return (1 - self.ax) * self.width
end

function Transform:localTop()
    return -self.height * self.ay
end

function Transform:localBottom()
    return self.height * (1 - self.ay)
end

function Transform:leftTop()
    self:checkUpdateTransform()
    local x = self.wx - self.ax * self.width * self.sx
    local y = self.wy - self.ay * self.height * self.sy
    return x, y
end
function Transform:iterEvent(eventID, x, y)
    if not self.active then
        return false
    end
    if self:hasCompoent(Scissor) then
        local renderInfo = self:getScissorInfo()
        pushScissor(renderInfo.args)
    end

    local blockByChild = false
    for i = #self.children, 1, -1 do
        local v = self.children[i]
        if v:iterEvent(eventID, x, y) and v.touchSwallowed then
            blockByChild = true
            break
        end
    end
    if self:hasCompoent(Scissor) then
        popScissor()
    end
    if self.interactable then
        if blockByChild and not self.ignoreChildBlock then
            return true
        else
            if self:hitTest(x, y) then
                self:emit(eventID, x, y)
                return true
            end
        end
    end
    return blockByChild
end

function Transform:mousepressed(x, y)
    return self:iterEvent(NodeEvent.MousePressed, x, y)
end

function Transform:mousemoved(x, y)
    return self:iterEvent(NodeEvent.MouseMoved, x, y)
end

function Transform:mousereleased(x, y)
    return self:iterEvent(NodeEvent.MouseReleased, x, y)
end

function Transform:touchpressed(x, y)
    return self:iterEvent(NodeEvent.TouchPressed, x, y)
end

function Transform:touchmoved(x, y)
    return self:iterEvent(NodeEvent.TouchMoved, x, y)
end

function Transform:touchreleased(x, y)
    return self:iterEvent(NodeEvent.TouchReleased, x, y)
end

function Transform:xy(x, y)
    self.x = x
    self.y = y
    return self
end

function Transform:wh(w, h)
    self.width, self.height = w, h
    return self
end

function Transform:axy(x, y)
    self.ax, self.ay = x, y
    return self
end

function Transform:getSiblingIndex()
    for i, v in ipairs(self.parent.children) do
        if v == self then
            return i
        end
    end
    return 0
end